package cn.melonlib.security.encryption.comp.enc.interceptor;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.codec.Base64;
import cn.melonlib.security.encryption.anno.Decrypt;
import cn.melonlib.security.encryption.anno.Encrypt;
import cn.melonlib.security.encryption.comp.enc.EncryptionService;
import cn.melonlib.security.encryption.enums.EncTarget;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.aop.framework.ReflectiveMethodInvocation;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

@Component
public class SecurityInterceptor extends ApplicationObjectSupport implements MethodInterceptor {

    @Resource
    private EncryptionService encryptionService;

    @Nullable
    @Override
    public Object invoke(@NotNull MethodInvocation invocation) throws Throwable {
        if(invocation instanceof ReflectiveMethodInvocation){
            return encdec(invocation);
        }
        return invocation.proceed();
    }

    /**
     * 加密和解密
     * @param invocation
     * @return
     * @throws Throwable
     */
    private Object encdec(@NotNull MethodInvocation invocation) throws Throwable {
        Method method=invocation.getMethod();
        List<Parameter> parameters=Arrays.stream(method.getParameters()).collect(Collectors.toList());
        List<Object> objects=Arrays.stream(invocation.getArguments())
                .reduce(new ArrayList<>(),(list,arg)->{
                    Object change=null;
                    Parameter parameter=parameters.get(list.size());
                    Encrypt encrypt=AnnotationUtils.findAnnotation(parameter, Encrypt.class);
                    Decrypt decrypt=AnnotationUtils.findAnnotation(parameter, Decrypt.class);
                    if(Objects.nonNull(encrypt)){
                        List<String> paths= Arrays.stream(encrypt.paths()).collect(Collectors.toList());
                        if(paths.isEmpty()) {
                            change = enc(arg, encrypt.target());
                        }else{
                            change=paths.stream().reduce(arg,(o,path)->{
                                Object val= BeanUtil.getProperty(o,path);
                                BeanUtil.setProperty(o,path,enc(val,encrypt.target()));
                                return o;
                            },(o,path)->null);
                        }
                    }else if(Objects.nonNull(decrypt)){
                        List<String> paths= Arrays.stream(decrypt.paths()).collect(Collectors.toList());
                        if(paths.isEmpty()){
                            change=dec(arg,decrypt.target());
                        }else{
                            change=paths.stream().reduce(arg,(o,path)->{
                                Object val=BeanUtil.getProperty(o,path);
                                BeanUtil.setProperty(o,path,dec(val,decrypt.target()));
                                return o;
                            },(o,path)->null);
                        }
                        change=dec(arg,decrypt.target());
                    }else{
                        change=arg;
                    }
                    list.add(change);
                    return list;
                },(list,arg)->null);
        ((ReflectiveMethodInvocation) invocation).setArguments(objects.toArray());
        Encrypt menc=AnnotationUtils.findAnnotation(method,Encrypt.class);
        Decrypt mdec=AnnotationUtils.findAnnotation(method,Decrypt.class);
        if(Objects.nonNull(menc)){
            List<String> paths= Arrays.stream(menc.paths()).collect(Collectors.toList());
            if(paths.isEmpty()) {
                return enc(invocation.proceed(), menc.target());
            }else{
                return paths.stream().reduce(invocation.proceed(),(o,path)->{
                    Object val= BeanUtil.getProperty(o,path);
                    BeanUtil.setProperty(o,path,enc(val,menc.target()));
                    return o;
                },(o,path)->null);
            }
        }else if(Objects.nonNull(mdec)){
            List<String> paths= Arrays.stream(mdec.paths()).collect(Collectors.toList());
            if(paths.isEmpty()) {
                return dec(invocation.proceed(), mdec.target());
            }else{
                return paths.stream().reduce(invocation.proceed(),(o,path)->{
                    Object val= BeanUtil.getProperty(o,path);
                    BeanUtil.setProperty(o,path,dec(val,mdec.target()));
                    return o;
                },(o,path)->null);
            }
        }else{
            return invocation.proceed();
        }

    }

    public Object enc(Object val, EncTarget encTarget){
        if(val instanceof CharSequence){
            try {
                switch (encTarget) {
                    case STORE:
                        return Base64.encodeUrlSafe(encryptionService.storeEnc(val.toString().getBytes(StandardCharsets.UTF_8)));
                    case TRANS:
                        return Base64.encodeUrlSafe(encryptionService.transEnc(val.toString().getBytes(StandardCharsets.UTF_8)));
                    default:
                        return val;
                }
            }catch (Exception e){
                logger.error(e.getMessage(),e);
                return val;
            }
        }else{
            return val;
        }
    }
    public Object dec(Object val, EncTarget encTarget){
        if(val instanceof CharSequence){
            try {
                switch (encTarget) {
                    case STORE:
                        return new String(encryptionService.storeDec(Base64.decode(val.toString())).getVal(), StandardCharsets.UTF_8);
                    case TRANS:
                        return new String(encryptionService.transDec(Base64.decode(val.toString())).getVal(), StandardCharsets.UTF_8);
                    default:
                        return val;
                }
            }catch (Exception e){
                logger.error(e.getMessage(),e);
                return val;
            }
        }else{
            return val;
        }
    }
}
